/*!
\copyright  Copyright (c) 2023 - 2025 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\brief      Support for downloadable capabilities that doesn't have dkcs headers.

Implementation is a simplified version of the custom operator library.
Instead of reading of dkcs headers to find what capabilities are available it is using a map provided through Downloadable_SetConfig().

*/

#include "downloadable.h"
#include "downloadable_file_private.h"
#include <file.h>
#include <vmal.h>
#include <logging.h>
#include <panic.h>

#include <stdlib.h>

typedef struct operator_list_t
{
    Operator operator;
    struct operator_list_t *next;
} operator_list_t;

typedef struct loaded_bundle_list_t
{
    /* The file index (file-system) of the bundle, used to uniquely identify a bundle file */
    FILE_INDEX file_index;
    /* A bundle handle used to unload a loaded bundle file */
    BundleID handle;
    /* The list of operators using the bundle */
    operator_list_t *head_of_operator_list;
    /* Next element in list */
    struct loaded_bundle_list_t *next;
    /* The number of capabilities offered by this bundle */
    uint32 num_cap_ids;
    /* The IDs of the capabilities offered by this bundle */
    uint16 capability_ids[];
} loaded_bundle_list_t;

typedef struct
{
    /* The number of capability IDs in all dkcs defined in the header */
    uint32 num_cap_ids;
    /* A list of capability IDs in all dkcs defined in the header */
    uint16 capability_ids[1];
} dkcs_header_t;

static loaded_bundle_list_t *head_of_bundle_list = NULL;

static const downlodable_map_config_t *map = NULL;

static loaded_bundle_list_t * downloadable_GetBundleNodeWithThisFileIndex(FILE_INDEX file_index)
{
    loaded_bundle_list_t *bundle_node;

    for(bundle_node = head_of_bundle_list; bundle_node != NULL; bundle_node = bundle_node->next)
    {
        if (bundle_node->file_index == file_index)
            return bundle_node;
    }

    return NULL;
}

static void downloadable_AddBundleNode(BundleID bundleId,
                          FILE_INDEX file_index,
                          dkcs_header_t *dkcs_header)
{
    unsigned cap_count;
    loaded_bundle_list_t *new_node = PanicUnlessMalloc(sizeof(*new_node)
                                     + (dkcs_header->num_cap_ids * sizeof(new_node->capability_ids[0])));

    new_node->file_index = file_index;
    new_node->handle = bundleId;
    new_node->head_of_operator_list = NULL;

    new_node->num_cap_ids = dkcs_header->num_cap_ids;
    for (cap_count = 0; cap_count < dkcs_header->num_cap_ids; cap_count++)
    {
        new_node->capability_ids[cap_count] = dkcs_header->capability_ids[cap_count];
    }

    new_node->next = head_of_bundle_list;
    head_of_bundle_list = new_node;
}

static bool downloadable_IsCapabilityIdInCapabilityIds(capability_id_t capability_id, uint16 *capability_ids, unsigned number_of_capability_ids)
{
    unsigned i;

    for(i = 0; i < number_of_capability_ids; i++)
    {
        if (capability_ids[i] == (uint16) capability_id)
            return TRUE;
    }

    return FALSE;
}

static void downloadable_DeleteFromOperatorList(operator_list_t *operator_node, operator_list_t **head_of_operator_list)
{
    operator_list_t **node;

    for(node = head_of_operator_list; *node != NULL; node = &(*node)->next)
    {
        if (*node == operator_node)
        {
            *node = (*node)->next;
            free(operator_node);
            return;
        }
    }
}

static void downloadable_FreeBundleNode(loaded_bundle_list_t *bundle_node)
{
    while(bundle_node->head_of_operator_list != NULL)
    {
        downloadable_DeleteFromOperatorList(bundle_node->head_of_operator_list, &bundle_node->head_of_operator_list);
    }

    free(bundle_node);
}

static void downloadable_DeleteFromBundleList(loaded_bundle_list_t *bundle_node)
{
    loaded_bundle_list_t **node;


    for(node = &head_of_bundle_list; *node != NULL; node = &(*node)->next)
    {
        if (*node == bundle_node)
        {
            *node = (*node)->next;
            downloadable_FreeBundleNode(bundle_node);
            return;
        }
    }
 }

static void downloadable_UnloadBundleIfNoUsers(loaded_bundle_list_t *bundle_node)
{
    if (bundle_node->head_of_operator_list == NULL)
    {
        PanicFalse(OperatorBundleUnload(bundle_node->handle));
        DEBUG_LOG_VERBOSE("downloadable_UnloadBundleIfNoUsers: unloaded bundle with file index %u", bundle_node->file_index);
        downloadable_DeleteFromBundleList(bundle_node);
    }
}

static bool downloadable_RemoveOperatorFromBundleNode(loaded_bundle_list_t *bundle_node, Operator operator)
{
    operator_list_t *operator_node;

    for(operator_node = bundle_node->head_of_operator_list; operator_node != NULL; operator_node = operator_node->next)
    {
        if (operator_node->operator == operator)
        {
            downloadable_DeleteFromOperatorList(operator_node, &bundle_node->head_of_operator_list);
            return TRUE;
        }
    }

    return FALSE;
}

static void downloadable_AddOperatorToBundleNode(Operator op, loaded_bundle_list_t *bundle_node)
{
    operator_list_t *new_node = PanicUnlessMalloc(sizeof(*new_node));

    new_node->operator = op;
    new_node->next = bundle_node->head_of_operator_list;
    bundle_node->head_of_operator_list = new_node;
}

static FILE_INDEX downloadable_FindCapabilityInLoadedBundles(capability_id_t cap_id)
{
    loaded_bundle_list_t *bundle_node;

    for(bundle_node = head_of_bundle_list; bundle_node != NULL; bundle_node = bundle_node->next)
    {
        if (downloadable_IsCapabilityIdInCapabilityIds(cap_id, bundle_node->capability_ids, bundle_node->num_cap_ids))
        {
            return bundle_node->file_index;
        }
    }

    return FILE_NONE;
}

static const char * downloadable_GetDownloadableFileName(uint16 cap_id)
{
    unsigned int i;

    PanicFalse(map != NULL);

    for (i = 0; i < map->number_of_downloadables; i++)
    {
        if (cap_id == map->downlodable_map[i].cap_id)
        {
            DEBUG_LOG_VERBOSE("getDownloadableFileName returning cap_id 0x%x, table index %d", cap_id, i);
            return map->downlodable_map[i].filename;
        }
    }
    return 0;
}

static FILE_INDEX downloadable_FindAndLoadBundleFromFilesystem(capability_id_t cap_id)
{
    const char *filename = downloadable_GetDownloadableFileName(cap_id);
    if(filename == NULL)
    {
        DEBUG_LOG_PANIC("downloadable_FindAndLoadBundleFromFilesystem: No filename for cap_id 0x%X", cap_id);
    }

    FILE_INDEX index = FileFind(FILE_ROOT, filename, (uint16)strlen(filename));
    if (index == FILE_NONE)
    {
        DEBUG_LOG_PANIC("downloadable_FindAndLoadBundleFromFilesystem: File %s not found", filename);
    }

    if(downloadable_GetBundleNodeWithThisFileIndex(index) == NULL)
    {
        BundleID bundle_id = OperatorBundleLoad(index, 0);
        if (bundle_id == BUNDLE_ID_INVALID)
        {
            DEBUG_LOG_PANIC("downloadable_FindAndLoadBundleFromFilesystem: Failed to load bundle file %s with index %u", filename, index);
        }
        DEBUG_LOG_VERBOSE("Loaded bundle index 0x%x, id 0x%x", index, bundle_id);

        dkcs_header_t dkcs_header = {0};
        dkcs_header.num_cap_ids = 1;
        dkcs_header.capability_ids[0] = cap_id;

        downloadable_AddBundleNode(bundle_id, index, &dkcs_header);
    }

    return index;
}

static void downloadable_AddOperatorToBundleFile(Operator op, FILE_INDEX bundle_file_index)
{
    loaded_bundle_list_t *bundle_node = PanicNull(downloadable_GetBundleNodeWithThisFileIndex(bundle_file_index));

    downloadable_AddOperatorToBundleNode(op, bundle_node);
}

static FILE_INDEX downloadable_LoadBundle(capability_id_t cap_id)
{
    FILE_INDEX file_index = downloadable_FindCapabilityInLoadedBundles(cap_id);

    if (file_index == FILE_NONE)
    {
        file_index = downloadable_FindAndLoadBundleFromFilesystem(cap_id);
    }

    PanicFalse(file_index != FILE_NONE);

    return file_index;
}

static void downloadable_UnloadBundle(Operator operator)
{
    loaded_bundle_list_t **bundle_node;

    for(bundle_node = &head_of_bundle_list; *bundle_node != NULL; bundle_node = &(*bundle_node)->next)
    {
        if (downloadable_RemoveOperatorFromBundleNode(*bundle_node, operator))
        {
            downloadable_UnloadBundleIfNoUsers(*bundle_node);
            break;
        }
    }
}

void Downloadable_SetConfig(const downlodable_map_config_t *config)
{
    map = config;
}

Operator Downloadable_CreateOperator(uint32 module_id, operator_priority_t priority)
{
    Operator op;
    FILE_INDEX bundle_file_index = FILE_NONE;
    capability_id_t cap_id = module_id & 0xffff;

    bundle_file_index = downloadable_LoadBundle(cap_id);
    op = OperatorsCreate(cap_id, 0, priority);
    if (bundle_file_index != FILE_NONE)
            downloadable_AddOperatorToBundleFile(op, bundle_file_index);
    return op;
}

void Downloadable_DestroyOperator(Operator operator)
{
    PanicFalse(OperatorDestroyMultiple(1, &operator, NULL));
    downloadable_UnloadBundle(operator);
    Downloadable_RemoveOperatorFileUser(operator);
}

#ifdef HOSTED_TEST_ENVIRONMENT
void Downloadable_TestReset(void)
{
    Downloadable_FileTestReset();
    while(head_of_bundle_list != NULL)
    {
        downloadable_DeleteFromBundleList(head_of_bundle_list);
    }
}
#endif

